package com.yunhe.abnormal.service.base.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunhe.abnormal.client.AuthorityClient;
import com.yunhe.abnormal.client.DeviceClient;
import com.yunhe.common.constant.KafkaConsts;
import com.yunhe.common.constant.SystemConsts;
import com.yunhe.abnormal.consts.AlarmConsts;
import com.yunhe.abnormal.domain.base.AlarmRule;
import com.yunhe.common.exception.ArgumentErrorException;
import com.yunhe.common.exception.ObjectNotFoundException;
import com.yunhe.common.exception.UniqueConstraintsException;
import com.yunhe.abnormal.mapper.base.AlarmRuleMapper;
import com.yunhe.abnormal.mapper.base.PointDataTypeMapper;
import com.yunhe.common.model.PageParam;
import com.yunhe.common.model.authority.StationDTO;
import com.yunhe.common.model.authority.UserDTO;
import com.yunhe.common.model.base.AlarmRuleDTO;
import com.yunhe.common.model.base.AlarmRuleInstanceDTO;
import com.yunhe.common.model.base.AlarmScopeDTO;
import com.yunhe.common.model.base.PointDataTypeDTO;
import com.yunhe.common.model.data.AlarmDpuFileDTO;
import com.yunhe.common.model.device.DeviceDTO;
import com.yunhe.common.model.msg.AlarmRuleMsgDTO;
import com.yunhe.common.relat.base.PointDataType;
import com.yunhe.abnormal.repository.base.AlarmRuleRepository;
import com.yunhe.abnormal.repository.base.AlarmRuleSession;
import com.yunhe.abnormal.service.base.AlarmRuleInstanceService;
import com.yunhe.abnormal.service.base.AlarmRuleService;
import com.yunhe.abnormal.service.base.AlarmScopeService;
import com.yunhe.abnormal.transaction.MultiTransaction;
import com.yunhe.abnormal.transaction.Neo4jTransaction;
import com.yunhe.common.util.*;
import net.oschina.j2cache.cache.support.util.SpringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import zju.fes.exp.Calculator;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 异常规则Service
 * @author liuronglei
 */
@Service
public class AlarmRuleServiceImpl implements AlarmRuleService {
    private static final Logger logger = LoggerFactory.getLogger(AlarmRuleServiceImpl.class);
    private ObjectMapper mapper = JsonUtil.getObjectMapper();

    @Autowired
    private AlarmRuleRepository alarmRuleRepository;
    @Autowired
    private AlarmRuleSession alarmRuleSession;
    @Autowired
    private AlarmScopeService alarmScopeService;
    @Autowired
    private AlarmRuleMapper alarmRuleMapper;
    @Autowired
    private PointDataTypeMapper pointDataTypeMapper;
    @Autowired
    private HeaderUtil headerUtil;
    @Autowired
    private AuthorityClient authorityClient;
    @Autowired
    private DeviceClient deviceClient;
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    @Autowired
    private AlarmRuleInstanceService alarmRuleInstanceService;

    private Calculator calculator = new Calculator();
    private static final String regex = "(?<=var\\()[\\w_\\u4e00-\\u9fa5\\-#/（）]+(?=\\))";

    /**
     * 异常规则对象处理和判断
     * @param alarmRule 异常规则对象
     */
    private void processBeforeSave(AlarmRule alarmRule)
            throws ArgumentErrorException, UniqueConstraintsException, ObjectNotFoundException {
        // 判断异常规则代码、编号、标题是否为空
        if (StringUtil.isEmpty(alarmRule.getDesc()) || StringUtil.isEmpty(alarmRule.getFormula())
                || alarmRule.getAlarmLevel() == null || alarmRule.getAlarmScope() == null
                || alarmRule.getAlarmCalcType() == null || alarmRule.getAlarmType() == null) {
            throw new ArgumentErrorException("作用对象、级别、类型、详情、判断规则、判断方式均不能为空");
        }
        // 判断是否有重复的公式存在
        Page<AlarmRule> pageByFormula = alarmRuleSession.findByCondition(null, null, null, null,
                null, null, null, alarmRule.getFormula(), null, null);
        boolean hasSameFormula = hasSameRule(pageByFormula, alarmRule);
        if (hasSameFormula) {
            throw new UniqueConstraintsException("已存在相同的判断规则");
        }
        // 判断是否有重复的异常类型和异常级别存在
        /*
        Page<AlarmRule> pageByTypeLevel = alarmRuleSession.findByCondition(null, null, null,
                Collections.singletonList(alarmRule.getAlarmLevel().getId()), null,
                Collections.singletonList(alarmRule.getAlarmType().getId()), null, null, null, null);
        boolean hasSameTypeLevel = hasSameRule(pageByTypeLevel, alarmRule);
        if (hasSameTypeLevel) {
            throw new UniqueConstraintsException("已存在相同异常类型和异常级别的规则");
        }
        */
        // 更新维护人和维护时间
        Long userId = headerUtil.getHeaderUserId();
        String userTitle = null;
        String timeZone = null;
        if (userId != null) {
            UserDTO user = authorityClient.getUserById(userId).getBody().getResults();
            if (user != null) {
                userTitle = user.getTitle();
                timeZone = user.getTimeZone();
            }
        }
        List<String> pointDataTypeTitle = getPointDataTypeTitle(alarmRule.getFormula());
        if (pointDataTypeTitle !=null && !pointDataTypeTitle.isEmpty()) {
            List<PointDataType> pointDataTypes = new ArrayList<>();
            for (String title : pointDataTypeTitle) {
                PointDataTypeDTO pointDataType = deviceClient.getPointDataTypeByTitle(title).getBody().getResults();
                if (pointDataType != null) {
                    pointDataTypes.add(pointDataTypeMapper.from(pointDataType));
                } else {
                    throw new ArgumentErrorException("找不到标题为“"+ title + "”的数据项");
                }
            }
            Map<String, Number> params = new HashMap<>();
            pointDataTypeTitle.forEach(item -> params.put(item, 1));
            try {
                calculator.eval(alarmRule.getFormula(), params);
            } catch (Exception ex) {
                ex.printStackTrace();
                throw new ArgumentErrorException("公式不符合计算逻辑");
            }
            alarmRule.setPointDataTypes(pointDataTypes);
        }
        Long rtime = System.currentTimeMillis();
        String dtime = DateTimeUtils.getDateString(rtime, timeZone);
        alarmRule.setRtime(rtime);
        alarmRule.setDtime(dtime);
        alarmRule.setUserId(userId);
        alarmRule.setUserTitle(userTitle);
    }

    /**
     * 获得用于保存时使用的异常规则对象（去掉关系，只保存属性）
     * @param alarmRule 异常规则对象
     */
    private AlarmRule getStoreAlarmRule(AlarmRule alarmRule) {
        AlarmRule storeAlarmRule = new AlarmRule();
        storeAlarmRule.setId(alarmRule.getId());
        storeAlarmRule.setTitle(alarmRule.getTitle());
        storeAlarmRule.setRtime(alarmRule.getRtime());
        storeAlarmRule.setDtime(alarmRule.getDtime());
        storeAlarmRule.setUserId(alarmRule.getUserId());
        storeAlarmRule.setUserTitle(alarmRule.getUserTitle());
        storeAlarmRule.setIsAllStation(alarmRule.getIsAllStation());
        storeAlarmRule.setIsAllDevice(alarmRule.getIsAllDevice());
        storeAlarmRule.setFormula(alarmRule.getFormula());
        storeAlarmRule.setDesc(alarmRule.getDesc());
        storeAlarmRule.setDurationRtime(alarmRule.getDurationRtime());
        return storeAlarmRule;
    }

    /**
     * 判断是否有重复的规则
     * @param page 查询结果
     * @param alarmRule 当前要判断的规则对象
     */
    private boolean hasSameRule(Page<AlarmRule> page, AlarmRule alarmRule) {
        boolean hasSameRule = false;
        if (page != null && page.getContent() != null && !page.getContent().isEmpty()) {
            if (alarmRule.getId() != null) {
                for (AlarmRule alarmRuleByFormula : page.getContent()) {
                    if (!alarmRuleByFormula.getId().equals(alarmRule.getId())) {
                        hasSameRule = true;
                        break;
                    }
                }
            } else {
                hasSameRule = true;
            }
        }
        return hasSameRule;
    }

    /**
     * 获得公式中对应的点号数据类型标题
     * @param formula 公式
     */
    private List<String> getPointDataTypeTitle(String formula) {
        List<String> result = new ArrayList<>();
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(formula);
        while (matcher.find()) {
            result.add(matcher.group());
        }
        return result;
    }

    /**
     * 新增异常规则对象
     * @param alarmRule 异常规则对象
     */
    private AlarmRuleDTO createOne(AlarmRuleDTO alarmRule) throws ArgumentErrorException, ObjectNotFoundException, UniqueConstraintsException {
        AlarmRule myAlarmRule = alarmRuleMapper.from(alarmRule);
        processBeforeSave(myAlarmRule);
        AlarmRule newRule = alarmRuleRepository.save(getStoreAlarmRule(myAlarmRule));
        myAlarmRule.setId(newRule.getId());
        alarmRuleSession.unBindRelat(myAlarmRule.getId());
        alarmRuleSession.bindRelat(myAlarmRule);
        return alarmRuleMapper.to(myAlarmRule);
    }

    /**
     * 新增异常规则对象
     * 单独拎出来，添加事务，与消息发送分开，避免消息接收时，对象还没创建完
     * @param alarmRule 异常规则对象
     */
    @MultiTransaction
    private AlarmRuleDTO doCreate(AlarmRuleDTO alarmRule) throws ArgumentErrorException, ObjectNotFoundException, UniqueConstraintsException {
        return createOne(alarmRule);
    }

    @MultiTransaction
    private List<AlarmRuleDTO> doCreate(List<AlarmRuleDTO> alarmRules) throws ArgumentErrorException, ObjectNotFoundException, UniqueConstraintsException {
        List<AlarmRuleDTO> result = new ArrayList<>();
        for (AlarmRuleDTO alarmRule : alarmRules) {
            result.add(createOne(alarmRule));
        }
        return result;
    }

    /**
     * 根据ID删除异常规则对象
     * @param id 对象ID
     */
    private AlarmRuleDTO deleteOne(Long id) throws ObjectNotFoundException {
        AlarmRuleDTO byId = SpringUtil.getBean(AlarmRuleService.class).findById(id);
        if (byId == null) {
            throw new ObjectNotFoundException("找不到ID为" + id + "的异常规则");
        }
        alarmRuleRepository.deleteById(id);
        return byId;
    }

    @MultiTransaction
    private AlarmRuleDTO doDelete(Long id) throws ObjectNotFoundException {
        return deleteOne(id);
    }

    @MultiTransaction
    private List<AlarmRuleDTO> doBatchDelete(List<Long> ids) throws ObjectNotFoundException {
        List<AlarmRuleDTO> result = new ArrayList<>();
        if (ids!=null && !ids.isEmpty()) {
            for (Long id : ids) {
                result.add(deleteOne(id));
            }
        }
        return result;
    }

    @MultiTransaction
    private AlarmRuleDTO doUpdate(AlarmRuleDTO alarmRule) throws ArgumentErrorException, ObjectNotFoundException, UniqueConstraintsException {
        AlarmRule byId = alarmRuleRepository.findById(alarmRule.getId()).orElse(null);
        if (byId == null) {
            throw new ObjectNotFoundException("找不到ID为" + alarmRule.getId() + "的异常规则");
        }
        AlarmRule myAlarmRule = alarmRuleMapper.from(alarmRule);
        processBeforeSave(myAlarmRule);
        AlarmRule newRule = alarmRuleRepository.save(getStoreAlarmRule(myAlarmRule));
        myAlarmRule.setId(newRule.getId());
        alarmRuleSession.unBindRelat(myAlarmRule.getId());
        alarmRuleSession.bindRelat(myAlarmRule);
        return alarmRuleMapper.to(myAlarmRule);
    }

    @Override
    public AlarmRuleDTO create(AlarmRuleDTO alarmRule) throws ArgumentErrorException, ObjectNotFoundException, UniqueConstraintsException {
        AlarmRuleDTO newAlarm = doCreate(alarmRule);
        // 发送新增消息
        sendAlarmRuleRefresh(Collections.singletonList(newAlarm), SystemConsts.ACTION.ADD);
        return newAlarm;
    }

    @Override
    public List<AlarmRuleDTO> create(List<AlarmRuleDTO> alarmRules) throws ArgumentErrorException, ObjectNotFoundException, UniqueConstraintsException {
        List<AlarmRuleDTO> result = doCreate(alarmRules);
        // 发送新增消息
        sendAlarmRuleRefresh(result, SystemConsts.ACTION.ADD);
        return result;
    }

    @Override
    public AlarmRuleDTO findById(Long id) {
        Optional<AlarmRule> obj = alarmRuleRepository.findById(id);
        return alarmRuleMapper.to(obj.orElse(null));
    }

    @Override
    public void deleteById(Long id) throws ObjectNotFoundException {
        AlarmRuleDTO deleteRule = doDelete(id);
        // 发送删除消息
        sendAlarmRuleRefresh(Collections.singletonList(deleteRule), SystemConsts.ACTION.DELETE);
    }

    @Override
    public void batchDelete(List<Long> ids) throws ObjectNotFoundException {
        List<AlarmRuleDTO> result = doBatchDelete(ids);
        // 发送删除消息
        sendAlarmRuleRefresh(result, SystemConsts.ACTION.DELETE);
    }

    @Override
    @MultiTransaction
    public void updateAlarmLevel(List<Long> ids, Long alarmLevelId) {
        alarmRuleSession.updateAlarmLevel(ids, alarmLevelId);
    }

    @Override
    public AlarmRuleDTO update(AlarmRuleDTO alarmRule) throws ArgumentErrorException, ObjectNotFoundException, UniqueConstraintsException {
        AlarmRuleDTO returnRule = doUpdate(alarmRule);
        // 发送修改消息
        sendAlarmRuleRefresh(Collections.singletonList(returnRule), SystemConsts.ACTION.EDIT);
        return returnRule;
    }

    @Override
    public Iterable<AlarmRuleDTO> search(String queryStr,
                                         List<Long> stationTypeId,
                                         List<Long> stationId,
                                         List<Long> alarmLevelId,
                                         List<Long> deviceTypeId,
                                         List<Long> alarmTypeId,
                                         Long alarmScopeId,
                                         List<String> dtime,
                                         PageParam pageParam) {
        List<Long> rtime = new ArrayList<>();
        if (dtime !=null && !dtime.isEmpty()) {
            for (String dtimeStr : dtime) {
                rtime.add(DateTimeUtils.getTimestamp(dtimeStr, null));
            }
        }
        Page<AlarmRule> page = alarmRuleSession.findByCondition(queryStr, stationTypeId, stationId,
                alarmLevelId, deviceTypeId, alarmTypeId, alarmScopeId, null, rtime, pageParam);
        List<AlarmRule> content = page.getContent();
        List<AlarmRuleDTO> to = alarmRuleMapper.to(content);
        return new PageImpl<>(to, page.getPageable(), page.getTotalElements());
    }

    @Override
    public boolean formulaCheck(String formula) throws ArgumentErrorException {
        Map<String, Number> params = new HashMap<>();
        List<String> pointDataTypeTitle = getPointDataTypeTitle(formula);
        for (String title : pointDataTypeTitle) {
            PointDataTypeDTO pointDataType = deviceClient.getPointDataTypeByTitle(title).getBody().getResults();
            if (pointDataType == null) {
                throw new ArgumentErrorException("找不到标题为“"+ title + "”的数据项");
            }
        }
        pointDataTypeTitle.forEach(item -> params.put(item, 1));
        try {
            calculator.eval(formula, params);
        } catch (Exception ex) {
            throw new ArgumentErrorException("公式不符合计算逻辑");
        }
        return true;
    }

    @Override
    public List<AlarmRuleDTO> findAlarmRuleForStation(Long stationId) {
        if (stationId == null) {
            return null;
        }
        StationDTO station = authorityClient.getStationById(stationId).getBody().getResults();
        if (station == null || station.getStationType() == null) {
            return null;
        }
        // 获得获得绑定为全部电站的规则
        AlarmScopeDTO alarmScope = alarmScopeService.findByName(AlarmConsts.AlarmScope.STATION.value());
        List<AlarmRule> noStationBind = alarmRuleSession.noStationBind(station.getStationType().getId(), alarmScope.getId());
        // 获得电站绑定的规则
        List<AlarmRule> stationBind = alarmRuleSession.getStationBind(stationId);
        // 整合前两者
        List<AlarmRule> all = new ArrayList<>();
        if (noStationBind != null) {
            all.addAll(noStationBind);
        }
        if (stationBind != null) {
            all.addAll(stationBind);
        }
        return alarmRuleMapper.to(all);
    }

    @Override
    public List<AlarmRuleDTO> findAlarmRuleForDevice(Long deviceId) {
        if (deviceId == null) {
            return null;
        }
        DeviceDTO device = deviceClient.getDeviceById(deviceId).getBody().getResults();
        if (device == null || device.getDeviceType() == null) {
            return null;
        }
        // 获得获得绑定为全部设备的规则
        AlarmScopeDTO alarmScope = alarmScopeService.findByName(AlarmConsts.AlarmScope.DEVICE.value());
        List<AlarmRule> noDeviceBind = alarmRuleSession.noDeviceBind((Long)device.getDeviceType().get("id"), alarmScope.getId());
        // 获得设备绑定的规则
        List<AlarmRule> deviceBind = alarmRuleSession.getDeviceBind(deviceId);
        // 整合前两者
        List<AlarmRule> all = new ArrayList<>();
        if (noDeviceBind != null) {
            all.addAll(noDeviceBind);
        }
        if (deviceBind != null) {
            all.addAll(deviceBind);
        }
        return alarmRuleMapper.to(all);
    }

    /**
     * 发送异常规则更新消息
     * @param alarmRules 异常规则列表
     * @param action 事件类型
     */
    private void sendAlarmRuleRefresh(List<AlarmRuleDTO> alarmRules, SystemConsts.ACTION action) {
        AlarmRuleMsgDTO msg = new AlarmRuleMsgDTO();
        msg.setAction(action.value());
        msg.setAlarmRules(alarmRules);
        try {
            kafkaTemplate.send(KafkaConsts.Topic.AlarmRuleRefresh, mapper.writeValueAsString(msg));
        } catch (Exception e) {
            logger.error("Kafka发送异常规则更新消息失败：{}" + alarmRules.toString());
            e.printStackTrace();
        }
    }

    /**
     * 下发异常规则到DPU
     */
    @Override
    @Neo4jTransaction
    public boolean sendAlarmRulesToDpu(Optional<Long> stationId,
                                       Optional<List<Long>> alarmRuleInstanceIds)
            throws JsonProcessingException, ArgumentErrorException {
        if (!stationId.isPresent() && !alarmRuleInstanceIds.isPresent()) {
            throw new ArgumentErrorException("请指定电站或告警规则实例");
        }
        List<AlarmRuleInstanceDTO> alarmRuleInstances;
        List<StationDTO> stations = new ArrayList<>();
        if (stationId.isPresent()) {
            alarmRuleInstances = alarmRuleInstanceService.findByStationId(stationId.get());
            StationDTO station = authorityClient.getStationById(stationId.get()).getBody().getResults();
            stations.add(station);
        } else {
            alarmRuleInstances = alarmRuleInstanceService.findByIds(alarmRuleInstanceIds.get());
            stations = authorityClient.searchStation(null).getBody().getResults();
        }
        Map<Long, Long> stationCodeMap = new HashMap<>(stations.size());
        Map<Long, List<String>> stationCodeBeeIdMap = new HashMap<>(stations.size());
        for (StationDTO station : stations) {
            stationCodeMap.put(station.getId(), station.getCode());
            stationCodeBeeIdMap.put(station.getCode(), station.getBeeIds());
        }
        Map<Long, List<Map<String, Object>>> result = new HashMap<>(stations.size());
        List<AlarmRuleInstanceDTO> alarmRuleInstanceList = new ArrayList<>();
        for (AlarmRuleInstanceDTO alarmRuleInstance : alarmRuleInstances) {
            // 异常规则为单个点号
            if (alarmRuleInstance.getPoints() != null && alarmRuleInstance.getPoints().size() == 1) {
                Long stationCode = stationCodeMap.get(alarmRuleInstance.getStationId());
                if (stationCode != null) {
                    Long pid = alarmRuleInstance.getPoints().get(0);
                    // 异常规则中不包含任何云端配置的虚拟点号
                    if (!String.valueOf(pid).startsWith(stationCode + "99999")) {
                        // 异常规则为判断逻辑（大于、小于、等于、△、逻辑或）
                        String formula = alarmRuleInstance.getFormula();
                        // 公式变换成DPU格式
                        String dpuFormula = convertToDpuFormula(formula);
                        String alarmRuleTitle = alarmRuleInstance.getAlarmRule().getTitle();
                        // 如果不存在，则生成虚拟点号
                        Long pointId;
                        if (alarmRuleInstance.getVirtualPointId() != null) {
                            pointId = alarmRuleInstance.getVirtualPointId();
                        } else {
                            pointId = alarmRuleInstanceService.getMaxAlarmVirtualPid(stationCode);
                            // 虚拟点号绑定告警实例
                            alarmRuleInstance.setVirtualPointId(pointId);
                            alarmRuleInstanceList.add(alarmRuleInstance);
                        }
                        Map<String, Object> map = new HashMap<>(3);
                        map.put("pid", pointId.toString());
                        map.put("formula", dpuFormula);
                        map.put("title", alarmRuleTitle);
                        List<Map<String, Object>> list = result.computeIfAbsent(stationCode, k -> new ArrayList<>());
                        list.add(map);
                    }
                }
            }
        }
        if (alarmRuleInstanceList.size() > 0) {
            // 保存虚拟点号绑定告警实例
            alarmRuleInstanceService.update(alarmRuleInstanceList);
        }
        for (Map.Entry<Long, List<Map<String, Object>>> entry : result.entrySet()) {
            AlarmDpuFileDTO alarmDpuFileDTO = new AlarmDpuFileDTO();
            alarmDpuFileDTO.setBeeIds(stationCodeBeeIdMap.get(entry.getKey()));
            alarmDpuFileDTO.setData(entry.getValue());
            kafkaTemplate.send(KafkaConsts.Topic.AlarmDpuFileRefresh, JsonUtil.toJson(alarmDpuFileDTO));
        }
        return true;
    }

    private static final Pattern pattern = Pattern.compile("var\\([^\\)]*\\)");

    private String convertToDpuFormula(String formula) {
        Matcher matcher = pattern.matcher(formula);
        Map<String, String> map = new HashMap<>();
        while (matcher.find()) {
            String varStr = matcher.group();
            String dpuStr = "$" + varStr.replace("var(", "").replace(")", "");
            map.put(varStr, dpuStr);
        }
        for (Map.Entry<String, String> entry : map.entrySet()) {
            formula = formula.replace(entry.getKey(), entry.getValue());
        }
        return formula;
    }
}
